package frontend;

import java.util.*;
import java.lang.reflect.Method;
import java.lang.reflect.Type;


public class SymbolTable {
	public SymbolTable parent = null;
	public SymbolTable child = null;
	public Hashtable<String, FunctionSymbolEntry> functionTable;
	public Hashtable<String, String> variableTable;
	public Hashtable<String, Integer> stocknameTable;
	
	public SymbolTable() {
		this.functionTable = new Hashtable<String, FunctionSymbolEntry>();
		this.variableTable = new Hashtable<String, String>();
		this.stocknameTable = new Hashtable<String, Integer>();
	}
	public boolean checkStockname(String name){
		if(stocknameTable.get(name) != null)
			return true;
		else
			return false;
	}
	public void importLib (String libName, String namespace, int removeArg)
    {
            try{
                    Class<?> c = Class.forName(libName);
                    Method[] allMethods = c.getDeclaredMethods();
                    for (Method m : allMethods) {
                            int modifier = m.getModifiers();
                            if ((modifier & 1) == 0 ) continue; // skip non-public methods
                            
                            if(m.getName()!="main"){
                                    String name="";
                                    if(namespace != "")
                                    	name = namespace+"."+m.getName();
                                    else 
                                    	name = m.getName();
                                    
                                  	Class<?>[] pType  = m.getParameterTypes();
                                  	String return_type = SyntaxTreeNodeUtil.TranslateFromJavaType(m.getReturnType().getName());
                                  	
                                   	FunctionSymbolEntry entry = new FunctionSymbolEntry(name, return_type, pType, removeArg);
                                   	functionTable.put(name, entry);
                             }
                    }
            }catch (ClassNotFoundException x) {
                    x.printStackTrace();
            }
    }
	public void init(SyntaxTreeNode root) {
        
        importLib("daytrader.lib.Indicators", "", 1);
        importLib("daytrader.lib.StockMath","Math", 0);
        importLib("daytrader.lib.Property","Property", 0);

		for (SyntaxTreeNode node : root.getChildren()) {
			if (node.getType() == SyntaxTreeNode.Type.USERDEFINE) {
				this.addAllCustomFunctions(node);
				return;
			}
		}
	}
	
	public void addAllCustomFunctions(SyntaxTreeNode userNode) {
		for (SyntaxTreeNode node : userNode.getChildren()) {
			if (node.getType() == SyntaxTreeNode.Type.USERFUNC) {
				this.addFunctionEntry(node);
			}
		}
	}

	public void addFunctionEntry(SyntaxTreeNode node) {
		String name = SyntaxTreeNodeUtil.getUserFuncName(node);
		
		// Check for duplicate function names
        if (functionTable.containsKey(name)) {
        	SemanticAnalyzer.errs.addError("Function '"
        			+ name + "' is defined more than once or conflicts "
        			+ "with internal function name.\nPlease note, overloading "
        			+ "is not supported for DayTrader.", node.getLineNumber());
        	return;
        }
        
		FunctionSymbolEntry entry = new FunctionSymbolEntry(node);
		this.functionTable.put(name, entry);
	}
	
	public Boolean isFunctionNameDefined(String name){
		return this.functionTable.containsKey(name);
	}
	
	// Determine the function call represent by a syntax tree node
	// can be find in the symbol table and with the matching 
	// signature
	public Boolean isFunctionValid(SyntaxTreeNode func) {
		String name = func.getValue().toString();
		if (!this.isFunctionNameDefined(name)) return false;
		
		FunctionSymbolEntry entry = this.functionTable.get(name);
		int count = entry.args.size();
		SyntaxTreeNode list_node = func.getFirstChild();
		if (list_node.getChildrenCount() != count) return false;
		
		for (int i = 0; i < count; i++) {
			String type_name = entry.args.get(i);
			SyntaxTreeNode arg_node = list_node.getChild(i);
			if (SyntaxTreeNodeUtil.isTypeCompatible(type_name, arg_node)) 
				return false;
		}
		return true;
	}
	
	// Search in symbol table hierarchy. Return empty string 
	// for not found
	public String getVariableType(String name) {
		SymbolTable t = this;
		do {
			if (t.variableTable.containsKey(name))
				return t.variableTable.get(name);
			t = t.parent;
		} while (t != null);
		return "";
	}
	
	public Boolean existVariable(String name) {
		return !(this.getVariableType(name).length()==0);
	}
	
	public void addVariable(String name, String type) {
		this.variableTable.put(name, type);
	}
	
	// Get the function/indicator return type from looking 
	// up the symbol table. Return empty string on not found
	public String getFunctionReturnType(String name) {
		SymbolTable root = this.getRootTable();
		if (root.functionTable.containsKey(name)) 
			return root.functionTable.get(name).returnType;
		return "";
	}
	public ArrayList<String> getFunctionArgList(String name) {
		SymbolTable root = this.getRootTable();
		if (root.functionTable.containsKey(name)) 
			return root.functionTable.get(name).args;
		return null;
	}
	
	// Get the top-most level symbol table
	public SymbolTable getRootTable() {
		SymbolTable t = this;
		while (t.parent != null) t = t.parent;
		return t;
	}
	
	/* test only methods */
	public void print() {
		for (String name : functionTable.keySet()) {
			String return_type = functionTable.get(name).returnType;
			System.out.print("function or indicator: " 
					+ return_type + " " + name + ", args: ");
			ArrayList<String> args = functionTable.get(name).args;
			for (String arg : args) {
				System.out.print(arg + ",");
			}
			System.out.println("");
		}
	}
	
	public void print_ids(String indent) {
		System.out.println("====================== print symbol table=================");
		for (String key : this.variableTable.keySet()) {
			System.out.println(indent + key + ":" + this.variableTable.get(key));
		}
		
		if (this.child != null) {
			this.child.print_ids(indent + "    ");
		}
	}
	
	public void createNestSymbolTable(){
		this.child = new SymbolTable();
		this.child.parent = this;
	}
	
	public void removeNestSymbolTable() {
		this.child = null;
	}
	
	public void clear() {
		this.child = null;
		this.functionTable.clear();
		this.variableTable.clear();
	}

}
